/**
 * =================================================================
 * 版权所有 2011-2013 深圳市泰海网络科技服务有限公司，并保留所有权利
 * -----------------------------------------------------------------
 * 这不是一个自由软件！您不能在任何未经允许的前提下对程序代码进行修改和使用；
 * 不允许对程序代码以任何形式任何目的的再发布
 * =================================================================
 */
package com.sfpay.api.internal.mapping;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

import com.sfpay.api.ApiException;
import com.sfpay.api.Constants;
import com.sfpay.api.SfpayResponse;
import com.sfpay.api.internal.util.StringUtils;

/**
 * 类说明：转换工具类。<br>
 * 
 * <p>
 * 详细描述：<br>
 * 
 * </p>
 * 
 * <pre>
 * ——————————————————————————————————————————————————————————————————
 * |		修改人		|		修改时间			|		修改原因
 * ——————————————————————————————————————————————————————————————————
 * |	zengxx 曾宪新	|		2013-8-18		|	
 * ——————————————————————————————————————————————————————————————————
 * </pre>
 * 
 * @author zengxx 曾宪新(Xavier.zeng)
 * 
 *         CreateDate: 2013-8-18
 */
public class Converters {

	/**
	 * 是否对JSON返回的数据类型进行校验，默认不校验。 给内部测试JSON返回时用的开关。
	 * 规则：返回的"基本"类型只有String,Long,Boolean,Date, 采取严格校验方式，如果类型不匹配，报错
	 */
	public static boolean isCheckJsonType = false;

	private static final Set<String> baseFields = new HashSet<String>();

	private Converters() {

	}

	/**
	 * 方法说明：使用指定 的读取器去转换字符串为对象。<br>
	 * 
	 * @param <T> 领域泛型
	 * @param clazz 领域类型
	 * @param reader  读取器
	 * @return 领域对象
	 * @throws ApiException
	 */
	public static <T> T convert(Class<T> clazz, Reader reader)
			throws ApiException {
		T rsp = null;

		try {
			rsp = clazz.newInstance();
			BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
			PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

			for (PropertyDescriptor pd : pds) {
				Method method = pd.getWriteMethod();
				if (null == method) {
					continue; // 忽略只读字段
				}

				String itemName = pd.getName();
				String listName = null;

				Field field = null;
				if (baseFields.contains(itemName)
						&& SfpayResponse.class.isAssignableFrom(clazz)) {
					field = SfpayResponse.class.getDeclaredField(itemName);
				} else {
					field = clazz.getDeclaredField(itemName);
				}
				
				ApiField jsonField = field.getAnnotation(ApiField.class);
				if (null != jsonField) {
					itemName = jsonField.value();
				}
				ApiListField jsonListField = field.getAnnotation(ApiListField.class);
				if (null != jsonListField) {
					listName = jsonListField.value();
				}
				
				if (!reader.hasReturnField(itemName)) {
					if (null == listName || !reader.hasReturnField(listName)) {
						continue; // 忽略只读字段
					}
				}
				
				Class<?> typeClass = field.getType();
				if (String.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof String) {
						method.invoke(rsp, value.toString());
					} else {
						if (isCheckJsonType && null != value) {
							throw new ApiException(itemName + " is not a String!");
						}
						if (null != value) {
							method.invoke(rsp, value.toString());
						} else {
							method.invoke(rsp, "");
						}
					}
				}
				else if (Long.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof Long) {
						method.invoke(rsp, (Long)value);
					} else {
						if (isCheckJsonType && null != value) {
							throw new ApiException(itemName + " is not a Number(Long)!");
						}
						if (StringUtils.isNumeric(value)) {
							method.invoke(rsp, Long.valueOf(value.toString()));
						}
					}
				}
				else if (Integer.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof Integer) {
						method.invoke(rsp, (Integer) value);
					} else {
						if (isCheckJsonType && value != null) {
							throw new ApiException(itemName + "  is not a Number(Integer)!");
						}
						if (StringUtils.isNumeric(value)) {
							method.invoke(rsp, Integer.valueOf(value.toString()));
						}
					}
				}
				else if (Boolean.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof Boolean) {
						method.invoke(rsp, (Boolean) value);
					} else {
						if (isCheckJsonType && value != null) {
							throw new ApiException(itemName + " is not a Boolean!");
						}
						if (value != null) {
							method.invoke(rsp, Boolean.valueOf(value.toString()));
						}
					}
				}
				else if (Double.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof Double) {
						method.invoke(rsp, (Double) value);
					} else {
						if (isCheckJsonType && value != null) {
							throw new ApiException(itemName + "  is not a Double!");
						}
					}
				}
				else if (Number.class.isAssignableFrom(typeClass)) {
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof Number) {
						method.invoke(rsp, (Number) value);
					} else {
						if (isCheckJsonType && value != null) {
							throw new ApiException(itemName + " is not a Number");
						}
					}
				}
				else if (Date.class.isAssignableFrom(typeClass)) {
					DateFormat format = new SimpleDateFormat(Constants.DATE_TIME_FORMAT);
					format.setTimeZone(TimeZone.getTimeZone(Constants.DATE_TIMEZONE));
					Object value = reader.getPrimitiveObject(itemName);
					if (value instanceof String) {
						method.invoke(rsp, format.parse(value.toString()));
					}
				}
				else if (List.class.isAssignableFrom(typeClass)) {
					Type fieldType = field.getGenericType();
					if (fieldType instanceof ParameterizedType) {
						ParameterizedType paramType = (ParameterizedType) fieldType;
						Type[] genericTypes = paramType.getActualTypeArguments();
						if (genericTypes != null && genericTypes.length > 0) {
							if (genericTypes[0] instanceof Class<?>) {
								Class<?> subType = (Class<?>) genericTypes[0];
								List<?> listObjs = reader.getListObjects(listName, itemName, subType);
								if (listObjs != null) {
									method.invoke(rsp, listObjs);
								}
							}
						}
					}
				} 
				else {
					Object obj = reader.getObject(itemName, typeClass);
					if (obj != null) {
						method.invoke(rsp, obj);
					}
				}
			}

		} catch (Exception e) {
			throw new ApiException(e);
		}

		return rsp;
	}

	static {
		baseFields.add("errorCode");
		baseFields.add("msg");
		baseFields.add("subCode");
		baseFields.add("subMsg");
		baseFields.add("body");
		baseFields.add("params");
		baseFields.add("success");
		baseFields.add("sfpForbiddenFields");
	}
}
